package com.wfw.config.logConfig;

import ch.qos.logback.classic.spi.CallerData;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.db.DBAppenderBase;
import ch.qos.logback.core.db.DBHelper;
import com.google.common.collect.Lists;
import com.google.gson.Gson;
import com.wfw.utils.IPUtil;
import com.wfw.utils.KeyUtil;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;

public class MyDBAppender extends DBAppenderBase<ILoggingEvent> {

    protected static final Method GET_GENERATED_KEYS_METHOD;

    //插入sql
    protected String insertSQL;
    //id
    static final int UUID_INDEX = 1;
    //类名
    static final int CLASSNAME_INDEX = 2;
    //方法名
    static final int METHOD_INDEX = 3;
    //信息级别
    static final int LOGLEVEL_INDEX = 4;
    //请求ip
    static final int REQUESTIP_INDEX = 5;
    //请求地址
    static final int URL_INDEX = 6;
    //请求参数
    static final int LOGPARAM_INDEX = 7;
    //返回对象
    static final int LOGRETURN_INDEX = 8;
    //请求uri
    static final int URI_INDEX = 9;
    //日志信息
    static final int MSG_INDEX = 10;
    //执行时间
    static final int EXECUTETIME_INDEX = 11;

    static final StackTraceElement EMPTY_CALLER_DATA = CallerData.naInstance();

    static {
        // PreparedStatement.getGeneratedKeys() method was added in JDK 1.4
        Method getGeneratedKeysMethod;
        try {
            // the
            getGeneratedKeysMethod = PreparedStatement.class.getMethod("getGeneratedKeys", (Class[]) null);
        } catch (Exception ex) {
            getGeneratedKeysMethod = null;
        }
        GET_GENERATED_KEYS_METHOD = getGeneratedKeysMethod;
    }

    @Override
    public void start() {
        StringBuilder sqlBuilder = new StringBuilder("INSERT INTO ");
        sqlBuilder.append("log_info").append(" (");
        sqlBuilder.append("id").append(", ");
        sqlBuilder.append("class_name").append(", ");
        sqlBuilder.append("method_name").append(", ");
        sqlBuilder.append("log_level").append(", ");
        sqlBuilder.append("request_ip").append(", ");
        sqlBuilder.append("url").append(", ");
        sqlBuilder.append("log_param").append(", ");
        sqlBuilder.append("log_return").append(", ");
        sqlBuilder.append("uri").append(", ");
        sqlBuilder.append("msg").append(", ");
        sqlBuilder.append("execute_time").append(") ");
        sqlBuilder.append("VALUES (?, ?, ? ,?, ?, ?, ?, ?, ?, ?, ?)");

        insertSQL = sqlBuilder.toString();
        super.start();
    }

    @Override
    protected Method getGeneratedKeysMethod() {
        return GET_GENERATED_KEYS_METHOD;
    }

    @Override
    protected String getInsertSQL() {
        return insertSQL;
    }

    @Override
    protected void subAppend(ILoggingEvent iLoggingEvent, Connection connection, PreparedStatement preparedStatement) throws Throwable {
        bindLoggingMyInfoWithPreparedStatement(preparedStatement, iLoggingEvent);
        bindLoggingEventWithInsertStatement(preparedStatement, iLoggingEvent);
        // This is expensive... should we do it every time?
        bindCallerDataWithPreparedStatement(preparedStatement, iLoggingEvent.getCallerData());
        int updateCount = preparedStatement.executeUpdate();
        if (updateCount != 1) {
            addWarn("Failed to insert loggingEvent");
        }

    }

    @Override
    protected void secondarySubAppend(ILoggingEvent iLoggingEvent, Connection connection, long l) throws Throwable {

    }

    //安全验证及个性化的数据
    void bindLoggingMyInfoWithPreparedStatement(PreparedStatement stmt, ILoggingEvent event)throws SQLException{
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//        String account = Base64Util.decodeBase64(request.getHeader("account"));
//        String passwd = Base64Util.decodeBase64(request.getHeader("passwd"));
//        String ip = Base64Util.decodeBase64(request.getHeader("serverIp"));
//        String appkey = request.getHeader("appkey");
//        stmt.setString(USERNAME_INDEX, account);
//        stmt.setString(PASSWORD_INDEX, passwd);
//        stmt.setString(SERVERIP_INDEX, ip);
//        stmt.setString(APPKEY_INDEX, appkey);
        stmt.setString(URL_INDEX,request.getRequestURL().toString());
        stmt.setString(URI_INDEX,request.getRequestURI());
        stmt.setString(REQUESTIP_INDEX, IPUtil.getIp(request));
//        stmt.setString(PROJECT_INDEX, "tmall-api");
    }

    void bindLoggingEventWithInsertStatement(PreparedStatement stmt, ILoggingEvent event) throws SQLException {
        stmt.setString(UUID_INDEX, KeyUtil.genUniqueKey());
        stmt.setString(MSG_INDEX, event.getFormattedMessage());
        stmt.setString(LOGLEVEL_INDEX, event.getLevel().toString());
        stmt.setString(LOGRETURN_INDEX,event.getMessage());
//        stmt.setString(EXECUTETIME_INDEX,String.valueOf(event.getTimeStamp()));
        stmt.setString(EXECUTETIME_INDEX,String.valueOf(event.getTimeStamp()-event.getLoggerContextVO().getBirthTime()));
        stmt.setString(CLASSNAME_INDEX, event.getLoggerName());

        Object[] args=event.getArgumentArray();
        if (args!=null){
            List<String> list = Lists.newArrayList();
            for (Object o : args) {
                list.add(new Gson().toJson(o));
            }
            stmt.setString(LOGPARAM_INDEX,list.toString());
        }else {
            stmt.setString(LOGPARAM_INDEX,null);
        }

    }

    void bindCallerDataWithPreparedStatement(PreparedStatement stmt, StackTraceElement[] callerDataArray) throws SQLException {

        StackTraceElement caller = extractFirstCaller(callerDataArray);

//        stmt.setString(CLASSNAME_INDEX, caller.getFileName());
        stmt.setString(METHOD_INDEX, caller.getMethodName());
    }

    private StackTraceElement extractFirstCaller(StackTraceElement[] callerDataArray) {
        StackTraceElement caller = EMPTY_CALLER_DATA;
        if (hasAtLeastOneNonNullElement(callerDataArray))
            caller = callerDataArray[0];
        return caller;
    }

    private boolean hasAtLeastOneNonNullElement(StackTraceElement[] callerDataArray) {
        return callerDataArray != null && callerDataArray.length > 0 && callerDataArray[0] != null;
    }

    /* (non-Javadoc)
     * @see ch.qos.logback.core.db.DBAppenderBase#append(java.lang.Object)
     */
    @Override
    public void append(ILoggingEvent eventObject) {
        Connection connection = null;
        try {
            connection = connectionSource.getConnection();
            connection.setAutoCommit(false);
            PreparedStatement insertStatement;
            insertStatement = connection.prepareStatement(getInsertSQL());
            // inserting an event and getting the result must be exclusive
            synchronized (this) {
                subAppend(eventObject, connection, insertStatement);
            }

            // we no longer need the insertStatement
            if (insertStatement != null) {
                insertStatement.close();
            }
            connection.commit();
        } catch (Throwable sqle) {
            addError("problem appending event", sqle);
        } finally {
            DBHelper.closeConnection(connection);
        }
    }
}
